import type { ParallelTaskExecuteContext } from '../handler'

import type {
  BusinessParams,
  LatRequesDataParams,
  LatRequesParams,
  LatResponseResult,
  SectionDelayParams,
  SystemConfig,
} from '../types'
import CryptoJS from 'crypto-js'
import { ParallelHandler } from '../handler'
import { LatRequesDataParamsStatusEnum, LatRequestStatusEnum } from '../types'
import { SectionDelay, toBase64 } from '../utils'

class LatRequest extends ParallelHandler<Array<number>, string> {
  private systemConfig!: SystemConfig

  private businessParams!: BusinessParams

  private sectionDelayParams!: Required<SectionDelayParams>

  private webSocketInstance: WebSocket | null = null

  private resultText: string = ''

  private lastResultText: string = ''

  private sectionDelayInstance: SectionDelay | null = null

  public initProperty(
    systemConfig: Required<SystemConfig>,
    businessParams: Required<BusinessParams>,
    sectionDelayParams: Required<SectionDelayParams>,
  ): void {
    this.systemConfig = { ...systemConfig }
    this.sectionDelayParams = { ...sectionDelayParams }
    this.businessParams = { ...businessParams }
  }

  public executePreCheck(): boolean {
    return this.webSocketInstance?.readyState === WebSocket.OPEN
  }

  protected onActive(): void {
    if (this.sectionDelayParams.autoControl) {
      const sectionDelayInstance = new SectionDelay({
        initialDelay: this.sectionDelayParams.initialDelay,
        subsequentDelay: this.sectionDelayParams.subsequentDelay,
        callback: () => {
          this.executeController?.end()
          this.sectionDelayInstance = null
        },
      })
      this.sectionDelayInstance = sectionDelayInstance
    }

    this.webSocketInstance = new WebSocket(this.generateRequestUrl())
    this.webSocketInstance.onmessage = (event: MessageEvent) => {
      const response = JSON.parse(event.data) as LatResponseResult
      const { resultText, lastResultText } = this.buildResponseText(response.data)

      this.resultText = resultText
      this.lastResultText = lastResultText

      this.executeController?.$bus.emit('_appResponseText', this.resultText)

      if (response.data.status === LatRequestStatusEnum.END) {
        this.executeController?.$bus.emit('_appResultText', this.resultText)
        this.batchTaskCompletedCallback(true)
      }
      else {
        if (this.sectionDelayParams.autoControl) {
          if (this.sectionDelayInstance) {
            this.sectionDelayInstance.debounce(this.resultText)
          }
        }
        this.batchTaskCompletedCallback(false)
      }
    }

    this.webSocketInstance.onopen = () => {
      if (this.sectionDelayParams.autoControl) {
        if (this.sectionDelayInstance) {
          this.sectionDelayInstance.debounce(this.resultText)
        }
      }
    }
  }

  public execute(context: ParallelTaskExecuteContext<Array<number>, never>): void {
    let dataStatus: LatRequesDataParamsStatusEnum = LatRequesDataParamsStatusEnum.CONTINUE

    if (context.isFirstExecute) {
      dataStatus = LatRequesDataParamsStatusEnum.START
    }
    if (context.isLastExecute) {
      dataStatus = LatRequesDataParamsStatusEnum.END
    }
    const requestParams = this.generateRequestParams(context.taskItem.original!, dataStatus)

    if (this.webSocketInstance) {
      this.webSocketInstance.send(JSON.stringify(requestParams))
    }
  }

  protected onFinish(): void {
    if (this.webSocketInstance) {
      this.webSocketInstance.close()
      this.webSocketInstance = null
    }
    this.lastResultText = ''
    this.resultText = ''
    this.executeController?.$bus.emit('_latRequestFinish')
  }

  private generateRequestUrl(): string {
    const apiKey = this.systemConfig.API_KEY
    const apiSecret = this.systemConfig.API_SECRET
    let url = 'wss://iat-api.xfyun.cn/v2/iat'
    const host = 'iat-api.xfyun.cn'
    const date = (new Date() as any).toGMTString()
    const algorithm = 'hmac-sha256'
    const headers = 'host date request-line'
    const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
    const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
    const signature = CryptoJS.enc.Base64.stringify(signatureSha)
    const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
    const authorization = btoa(authorizationOrigin)
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
    return url
  }

  public buildResponseText(data: LatResponseResult['data']): {
    resultText: string
    lastResultText: string
  } {
    let { resultText, lastResultText } = this

    if (data?.result) {
      const { result } = data
      const { ws } = result
      const currentText = ws.map(w => w.cw[0].w).join('')

      if (Reflect.has(result, 'pgs')) {
        if (result.pgs === 'apd') {
          lastResultText = resultText
          resultText = lastResultText + currentText
        }
        else {
          resultText = lastResultText + currentText
        }
      }
      else {
        resultText += currentText
      }
    }

    return {
      resultText,
      lastResultText,
    }
  }

  private generateRequestParams(audioData: Array<number>, status: LatRequesDataParamsStatusEnum): LatRequesParams | {
    data: LatRequesDataParams
  } {
    const data = {
      status,
      format: 'audio/L16;rate=16000',
      encoding: 'raw',
      audio: toBase64(audioData),
    } as LatRequesDataParams

    if (status === LatRequesDataParamsStatusEnum.START) {
      return {
        common: {
          app_id: this.systemConfig.APPID,
        },
        business: {
          ...this.businessParams,
        },
        data: {
          ...data,
        },
      }
    }
    else if (status === LatRequesDataParamsStatusEnum.END) {
      return {
        data: {
          ...data,
        },
      }
    }
    else {
      return {
        data: {
          ...data,
        },
      }
    }
  }
}

export default LatRequest
